#pragma once
// -------------------------------------------------
// ZED Kit
// -------------------------------------------------
//   File Name: file.hpp
//      Author: Ziming Li
//     Created: 2021-02-11
// -------------------------------------------------
// Copyright (C) 2021 MingYang Software Technology.
// -------------------------------------------------

#ifndef ZED_FILE_FILE_HPP
#define ZED_FILE_FILE_HPP

#include <cstdio>
#include <string>
#include <vector>
#include "../assert.h"
#include "../platform_sdk.h"
#ifdef _Z_OS_WINDOWS
#   include "../win/handled_resource.hpp"
#else
#   include "../memory.hpp"
#endif

namespace zed {

template <>
struct default_resource_finalizer<FILE *>
{
    void operator()(FILE *fp) const { ::fclose(fp); }
};

#ifdef _Z_OS_WINDOWS
using unique_file = unique_resource<HANDLE, default_resource_finalizer<HANDLE>, file_handle_traits>;
#else
using unique_file = unique_resource<FILE *>;
#endif

class file
{
public:
    file(file &&f) : m_file(std::move(f.m_file)) {}

#ifdef _Z_OS_WINDOWS
    using path_t = PCWSTR;
#else
    using path_t = const char *;
#endif

    operator bool() const { return !!m_file; }
    size_t size(void);
    size_t read(void *dst, size_t cb);
    size_t write(const void *data, size_t cb);
    int64_t tell(void);
    int64_t seek(int64_t offset, int origin);

    enum class mode_t {
        open_existing, write_new,
    };
    static file open(path_t path, mode_t mode);

    static bool read(path_t path, std::string &dst);
    static bool read(path_t path, std::vector<unsigned char> &dst);

    static bool write(path_t path, const void *data, size_t size);

    static bool remove(path_t path);
private:
    file(unique_file &uf) : m_file(std::move(uf)) {}

    unique_file m_file;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace detail {

template <class DST>
bool read_file(file::path_t path, DST &dst)
{
    static_assert(1 == sizeof(typename DST::value_type), "Unexpected data type!");

    if (file f = file::open(path, file::mode_t::open_existing))
    {
        dst.clear();

        size_t size = f.size();
        if (0 == size)
            return true;

        dst.resize(size);
        return f.read(const_cast<typename DST::value_type *>(dst.data()), size) == size;
    }

    return false;
}

} // namespace detail

#ifdef _Z_OS_WINDOWS
inline file file::open(path_t path, mode_t mode)
{
    unique_file ret;

    DWORD access, share_mode = 0, creation_dispostion, attributes = 0;
    switch (mode)
    {
        case mode_t::open_existing:
            access = GENERIC_READ;
            share_mode = FILE_SHARE_READ;
            creation_dispostion = OPEN_EXISTING;
            break;
        case mode_t::write_new:
            access = GENERIC_WRITE;
            creation_dispostion = CREATE_ALWAYS;
            attributes = FILE_ATTRIBUTE_ARCHIVE;
            break;
        default:
            ZASSERT(false); // Not reached!
            return file(ret);
    }

    ret.reset(::CreateFileW(path, access, share_mode, nullptr, creation_dispostion, attributes, nullptr));
    return file(ret);
}

inline size_t file::read(void *dst, size_t cb)
{
    DWORD dw;
    return ::ReadFile(m_file.get(), dst, static_cast<DWORD>(cb), &dw, nullptr) ? dw : 0;
}

inline bool file::remove(path_t path)
{
    return ::DeleteFileW(path);
}

inline int64_t file::seek(int64_t offset, int origin)
{
    DWORD method;
    switch (origin)
    {
        case SEEK_SET:
            method = FILE_BEGIN;
            break;
        case SEEK_CUR:
            method = FILE_CURRENT;
            break;
        case SEEK_END:
            method = FILE_END;
            break;
        default:
            ZASSERT(false); // Invalid origin!
            return 0;
    }

    LARGE_INTEGER li;
    li.QuadPart = offset;
    return SetFilePointerEx(m_file.get(), li, nullptr, method) ? 0 : -1;
}

inline size_t file::size(void)
{
    return GetFileSize(m_file.get(), nullptr);
}

inline int64_t file::tell(void)
{
    LARGE_INTEGER offset = { 0 }, ret;
    if (!SetFilePointerEx(m_file.get(), offset, &ret, FILE_CURRENT))
        return -1;
    return ret.QuadPart;
}

inline size_t file::write(const void *data, size_t cb)
{
    DWORD dw;
    return ::WriteFile(m_file.get(), data, static_cast<DWORD>(cb), &dw, nullptr) ? dw : 0;
}
#else
inline file file::open(path_t path, mode_t mode)
{
    unique_file ret;

    const char *m;
    switch (mode)
    {
        case mode_t::open_existing:
            m = "rb";
            break;
        case mode_t::write_new:
            m = "wb";
            break;
        default:
            ZASSERT(false); // Not reached!
            return file(ret);
    }

    ret.reset(::fopen(path, m));
    return file(ret);
}

inline size_t file::read(void *dst, size_t cb)
{
    return ::fread(dst, 1, cb, m_file.get());
}

inline int64_t file::seek(int64_t offset, int origin)
{
    return ::fseek(m_file.get(), offset, origin);
}

inline size_t file::size(void)
{
    size_t ret;

    size_t pos = ::ftell(m_file.get());
    ::fseek(m_file.get(), 0, SEEK_END);
    ret = ::ftell(m_file.get());
    ::fseek(m_file.get(), pos, SEEK_SET);
    return ret;
}

inline int64_t file::tell(void)
{
    return ::ftell(m_file.get());
}

inline size_t file::write(const void *data, size_t cb)
{
    return ::fwrite(data, 1, cb, m_file.get());
}
#endif // _Z_OS_WINDOWS

inline bool file::read(path_t path, std::string &dst)
{
    return detail::read_file(path, dst);
}

inline bool file::read(path_t path, std::vector<unsigned char> &dst)
{
    return detail::read_file(path, dst);
}

inline bool file::write(path_t path, const void *data, size_t size)
{
    if (file f = open(path, mode_t::write_new))
        return f.write(data, size);
    return false;
}

} // namespace zed

#endif // ZED_FILE_FILE_HPP
